/* -*- P4_16 -*- */
#include <core.p4>
#include <v1model.p4>


/*************************************************************************
*********************** H E A D E R S  ***********************************
*************************************************************************/

typedef bit<9>  egressSpec_t;
typedef bit<48> macAddr_t;
typedef bit<32> ip4Addr_t;

header ethernet_t {
    macAddr_t dstAddr;
    macAddr_t srcAddr;
    bit<16>   etherType;
}

/*
 * TODO: split tos to two fields 6 bit diffserv and 2 bit ecn
 */
header ipv4_t {
    bit<4>    version;
    bit<4>    ihl;
    bit<8>    diffserv;
    bit<8>    ecn;
    bit<16>   totalLen;
    bit<16>   identification;
    bit<3>    flags;
    bit<13>   fragOffset;
    bit<8>    ttl;
    bit<8>    protocol;
    bit<16>   hdrChecksum;
    ip4Addr_t srcAddr;
    ip4Addr_t dstAddr;
}



struct metadata {
    bit<19> ecn_threshold;
}

struct ecn_digest {
    bit<32> queue_depth;
    bit<8>  ecn_value;
}

struct headers {
    ethernet_t   ethernet;
    ipv4_t       ipv4;
}

/*************************************************************************
*********************** P A R S E R  ***********************************
*************************************************************************/

parser MyParser(packet_in packet,
                  out headers hdr,
                  inout metadata meta,
                  inout standard_metadata_t standard_metadata) {

    state start {
	packet.extract(hdr.ethernet);
        transition select(hdr.ethernet.etherType){
	    0x0800: parse_ipv4;
	    default:accept;
	}
    }


    state parse_ipv4 {
        packet.extract(hdr.ipv4);
        transition accept;
    }
}


/*************************************************************************
************   C H E C K S U M    V E R I F I C A T I O N   *************
*************************************************************************/

control MyVerifyChecksum(inout headers hdr, inout metadata meta) {
    apply {  }
}
/*************************************************************************
**************  I N G R E S S   P R O C E S S I N G   *******************
*************************************************************************/

control MyIngress(inout headers hdr,
                  inout metadata meta,
                  inout standard_metadata_t standard_metadata) {
    action ipv4_forward(bit<48> dstAddr, bit<9> port){
	hdr.ethernet.dstAddr = dstAddr;
	standard_metadata.egress_spec = port;
    }

    action drop(){
	mark_to_drop(standard_metadata);
    }
    
    action mark_ecn() {
        hdr.ipv4.ecn = 0x03;
    }

    action set_ecn_threshold(bit<19> threshold){
	meta.ecn_threshold = threshold;
    }

    action generate_digest(bit<32> qdepth, bit<8> ecn){
	digest<ecn_digest>(
	    1,
	    {qdepth, ecn}
	);
    }

    table ipv4_lpm{
	key={
	    hdr.ipv4.dstAddr: lpm;
	}
	actions = {
	    ipv4_forward;
	    drop;
	}
	size = 1024;
	default_action = drop;
    }
    
    table ecn_marking {
	key = {
	    standard_metadata.enq_qdepth : exact;
	}
	actions = {
	    mark_ecn;
	    NoAction;
	}
	const default_action = NoAction;
    }

    table set_threshold {
	key = {
	    hdr.ipv4.protocol: exact;
	}
	actions = {
	    set_ecn_threshold;
	    NoAction;
	}
	const default_action =NoAction;
    }

    table check_congestion {
	key = {
	    hdr.ipv4.ecn: exact;
	}
	actions = {
	    generate_digest;
	    NoAction;
	}
	const default_action = NoAction;
    }
    
    apply{
	set_threshold.apply();
	if (standard_metadata.enq_qdepth >= meta.ecn_threshold){
	    ecn_marking.apply();
	}

	if(hdr.ipv4.ecn == 0x03){
	    check_congestion.apply();
	}

	ipv4_lpm.apply();
    }
}

/*************************************************************************
****************  E G R E S S   P R O C E S S I N G   *******************
*************************************************************************/

control MyEgress(inout headers hdr,
                 inout metadata meta,
                 inout standard_metadata_t standard_metadata) {
    apply {
	    }
	}
    
        /*
         * TODO:
         * - if ecn is 1 or 2
         *   - compare standard_metadata.enq_qdepth with threshold
         *     and set hdr.ipv4.ecn to 3 if larger
         */

/*************************************************************************
*************   C H E C K S U M    C O M P U T A T I O N   **************
*************************************************************************/
control MyComputeChecksum(inout headers hdr, inout metadata meta) {
     apply {
        update_checksum(
            hdr.ipv4.isValid(),
            { hdr.ipv4.version,
              hdr.ipv4.ihl,
              hdr.ipv4.diffserv,
              hdr.ipv4.ecn,
              hdr.ipv4.totalLen,
              hdr.ipv4.identification,
              hdr.ipv4.flags,
              hdr.ipv4.fragOffset,
              hdr.ipv4.ttl,
              hdr.ipv4.protocol,
              hdr.ipv4.srcAddr,
              hdr.ipv4.dstAddr },
            hdr.ipv4.hdrChecksum,
            HashAlgorithm.csum16);
    }
}



/*************************************************************************
***********************  D E P A R S E R  *******************************
*************************************************************************/

control MyDeparser(packet_out packet, in headers hdr) {
    apply {
        packet.emit(hdr.ethernet);	
	packet.emit(hdr.ipv4);
	}
}

/*************************************************************************
***********************  S W I T C H  *******************************
*************************************************************************/

V1Switch(
MyParser(),
MyVerifyChecksum(),
MyIngress(),
MyEgress(),
MyComputeChecksum(),
MyDeparser()
) main;
